//-< MAIN.CPP >------------------------------------------------------*--------*
// GigaBASE                  Version 1.0         (c) 1999  GARRET    *     ?  *
// (Post Relational Database Management System)                      *   /\|  *
//                                                                   *  /  \  *
//                          Created:     22-Jan-2012   K.A. Knizhnik * / [] \ *
//                          Last update: 22-Jan-2012   K.A. Knizhnik * GARRET *
//-------------------------------------------------------------------*--------*
// Data providers for StockDB
//-------------------------------------------------------------------*--------*

#include "stockdb.h"
#include "sockio.h"
#include <errno.h>

const int    MAX_CONNECT_ATTEMPTS = 10;
const int    RECONNECT_TIMEOUT = 1;
const size_t MAX_ERROR_MSG_LENGTH = 256;
const size_t HTTP_REQ_BUF_SIZE = 256;
const size_t HTTP_RESP_BUF_SIZE = 64*1024;

static ErrorCode readChunked(string& error, FILE* out, socket_t* s) 
{ 
    char errorBuf[MAX_ERROR_MSG_LENGTH];
    char resp[HTTP_RESP_BUF_SIZE+3];
    resp[HTTP_RESP_BUF_SIZE] = '\n';
    resp[HTTP_RESP_BUF_SIZE+1] = '\r';
    resp[HTTP_RESP_BUF_SIZE+2] = '\n';
    int rc = s->read(resp, 1, HTTP_RESP_BUF_SIZE);
    float httpVersion;
    int status;
    if (rc < 0) {
        s->get_error_text(errorBuf, sizeof(errorBuf));
        error = StockDB::format("Failed to read data: %s", errorBuf);
        return NETWORK_ERROR;
    }
    
    if (sscanf(resp, "HTTP/%f %d", &httpVersion, &status) != 2) { 
        error = StockDB::format("Bad HTTP response: %s", resp);
        return BAD_RESPONSE;
    }
    if (status == 404) { 
        error = "Symbol not found";
        return SYMBOL_NOT_FOUND;
    }
    if (status != 200) { 
        error = StockDB::format("Provider returns status %d", status);
        return PROVIDER_ERROR;
    }        

    int i = 0;
    while (resp[i] != '\n' || resp[i+1] != '\r' || resp[i+2] != '\n') { 
        i += 1;
    }
    if (++i > rc) { 
        error = "Bad HTTP response";
        return BAD_RESPONSE;
    }
    int count, n;
    while (true) { 
        assert(i <= rc);
        resp[rc] = '\0';
        if (rc - i < 5 || strchr(&resp[i+2], '\n') == NULL) { 
            memmove(resp, resp+i, rc-i);
            i = rc - i;
            rc = s->read(&resp[i], 1, HTTP_RESP_BUF_SIZE-i);
            if (rc < 0) {
                s->get_error_text(errorBuf, sizeof(errorBuf));
                error = StockDB::format("Failed to read data: %s", errorBuf);
                return NETWORK_ERROR;
            }
            rc += i;
            i = 0;
        }
        i += 2; // skip CLRF
        if (sscanf(&resp[i], "%x%n", &count, &n) != 1) { 
            error = StockDB::format("Chunked encoding error at postion %d in %s", i, resp);
            return BAD_RESPONSE;
        }
        assert(count < 1000000);
        if (count == 0) { 
            break;
        }
        i += n + 2; // skip CRLF
        while (true) { 
            int available = rc - i;
            assert(available >= 0);
            if (available > count) { 
                available = count;
            }
            if (int(fwrite(&resp[i], 1, available, out)) != available) { 
                error = StockDB::format("File write error: %d", errno);
                return FILE_WRITE_ERROR;
            }
            count -= available;
            i += available;
            if (count == 0) { 
                break;
            }
            i = 0;
            rc = s->read(resp, 1, HTTP_RESP_BUF_SIZE);
            if (rc < 0) {
                s->get_error_text(errorBuf, sizeof(errorBuf));
                error = StockDB::format("Failed to read data: %s", errorBuf);
                return NETWORK_ERROR;
            }
        }
    }
    return OK;
}

ErrorCode YahooStockDataProvider::download(string& file, string& error, string const& symbol, date_t from, date_t till)
{
    char req[HTTP_REQ_BUF_SIZE];
    char timeBuf[2][TIME_BUF_SIZE];
    char errorBuf[MAX_ERROR_MSG_LENGTH];
    file = StockDB::format("%s_%s_%s.csv", symbol.c_str(), StockDB::printDate(from, timeBuf[0], sizeof(timeBuf[0])), 
                           StockDB::printDate(till != 0 ? till : date_t(time(NULL)), timeBuf[1], sizeof(timeBuf[1])));
    if (cache) { 
        FILE* in = fopen(file.c_str(), "r");
        if (in != NULL) { 
            // file exists
            fclose(in);
            return OK;
        }
    }
    FILE* out = fopen(file.c_str(), "w");
    if (out == NULL) {
        error = StockDB::format("Failed to create file '%s'", file.c_str());
        return FILE_NOT_FOUND;
    }
    int n = sprintf(req, "GET /table.csv?s=%s", symbol.c_str());
    if (from != 0) {
        dbDateTime fromDate((time_t)(from));
        n += sprintf(req + n, "&a=%d&b=%d&c=%d", fromDate.month()-1, fromDate.day(), fromDate.year());
    }
    if (till != 0) {
        dbDateTime tillDate((time_t)(till));
        n += sprintf(req + n, "&d=%d&e=%d&f=%d", tillDate.month()-1, tillDate.day(), tillDate.year());
    }
    n += sprintf(req + n, "&g=d HTTP/1.1\r\nHost: ichart.finance.yahoo.com\r\nAccept: text/html\r\nAccept-Language: en-us,en\r\nAccept-Charset: ISO-8859-1,utf-8\r\n\r\n");
    socket_t* s = socket_t::connect("ichart.finance.yahoo.com:80", socket_t::sock_global_domain, MAX_CONNECT_ATTEMPTS, RECONNECT_TIMEOUT);
    if (s == NULL || !s->is_ok()) { 
        if (s != NULL) { 
            s->get_error_text(errorBuf, sizeof(errorBuf));
            delete s;
            error = StockDB::format("Failed to connect to server: %s", errorBuf);
        } else { 
            error = "Failed to create socket"; 
        }
        fclose(out);
        return NETWORK_ERROR;
    }
    if (!s->write(req, n)) { 
        s->get_error_text(errorBuf, sizeof(errorBuf));
        error = StockDB::format("Failed to send request to server: %s", errorBuf);
        delete s;
        fclose(out);
        return NETWORK_ERROR;
    }
    ErrorCode err = readChunked(error, out, s); 
    fclose(out);
    delete s;
    return err;
}

void YahooStockDataProvider::releaseFile(string const& file)
{
    if (!cache) { 
        unlink(file.c_str());
    }
}

bool YahooStockDataProvider::descendingOrder()
{
    return true;
}


